iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 19
0
AI & Data

強化學習系列 第 19

強化學習筆記 Day 19

  • 分享至 

  • xImage
  •  

前言

昨天我們提到 https://chart.googleapis.com/chart?cht=tx&chl=%5Cepsilon -greedy 方法,讓我們的模型有一定的機會,嘗試執行沒有用過的動作,增加模型找到真正最佳動作的機會。

實作 Monte Carlo Control

依照昨天所說明的內容,以及書籍中的流程圖,我們可以實作蒙地卡羅控制法,於之前的 GridWorld 中。定義了以下的函數幫助我們作業

狀態轉移模擬

我們定義一個函數,模擬在環境中運行的情況,並將模擬中經過的狀態、決策、回饋... 等資訊存下來,供之後更新使用。這邊我們一樣有設定最高執行 50 次的設定,目的在於避免沒有抵達終點的情況。

def SimProc(init_state, init_action, policy, steps, gamma, reward, trans_mat):
    record = []
    state = init_state
    action = init_action
    for step in range(steps):
        # get next information
        next_state = np.argmax(trans_mat[:, state, action])
        next_action = policy[next_state]
        record.append([state, action, reward[next_state]])
        # update information
        state = next_state
        action = next_action
        if state == 0 or state == 15:
            break
        if step > 50:
            print('Over 50 steps')
    return record

計算價值

進行玩模擬後,我們要計算這次模擬(之前有說過這個稱為一個 episode)中,所執行的動作其價值為多少。這裡有兩個函數, GetValue 函數是負責計算某個位置使用某個動作的價值, EpisodeValue 函數用於控制計算進行 (ex: 不重複計算已經計算過的動作價值)。

我們這邊不重複計算已經計算過的動作價值,是因為採用 first-visit 原則,與之相對的是 every-visit 原則。在同一個 episode 中,我們可能會重複經過某些狀態,在後續計算動作價值時,有兩種方案:

  • first-visit 指的是,只計算第一次到這個作態、採取這個動作,所得到的動作價值。
  • every-visit 指的是,計算每次到這個狀態、採去這個動作,所得到的動作價值。

因此,在一個 episode 中,使用 first-visit 原則時,相同的狀態、動作只會有一個動作價值。而 every-visit 原則則可能會有多個動作價值。
實作上, fisit-visit 要記錄是否有經過某個狀態、採取某個動作,而 every-visit 則要紀錄某個狀態、採取某個動作的次數。

def GetValue(records, gamma):
    counter = 0
    value = 0
    for record in records:
        reward = record[-1]
        value += reward*pow(gamma, counter)
        counter += 1
    return value

def EpisodeValue(records, gamma):
    episode_visited = np.zeros([16, 4])
    episode_value = np.zeros([16, 4])
    for counter in range(len(records)):
        state = records[counter][0]
        action = records[counter][1]
        if episode_visited[state, action] == 1:
            continue
        episode_value[state, action] = GetValue(records[counter:], gamma)
        episode_visited[state, action] = 1
    return episode_value, episode_visited

更新動作價值、決定下次策略

透過不同模擬,我們可以累積許多 episode 的資料,用來更新動作價值,並使用新的動作價值決定新的策略。

def UpdateActionValue(episode_value, episode_visited, action_value, total_visited):
    total_value = (action_value*total_visited) + episode_value
    total_visited += episode_visited
    rst = total_value / total_visited
    action_value = np.nan_to_num(rst)
    return action_value, total_visited

def NextPolicy(action_value, epsilon):
    if np.random.rand(1) < epsilon:
        policy = np.random.randint(0,4,16)
        explore = True
    else:
        policy = np.argmax(action_value, axis = 1)
        explore = False
    return policy, explore

開始模擬

在 main 函數中設定一些環境與模擬的參數

def main(Episodes, InitState, InitAction, InitEpsilon):
    # environment setting
    Policy = np.random.randint(0, 4, 16)
    ActionValue = np.zeros([16, 4])
    TotalVisited = np.zeros([16, 4])
    Reward = np.full(16, -1)
    Reward[0] = 0
    Reward[-1] = 0
    TransMat = np.load('./gridworld/T.npy')
    # parameters setting
    GAMMA = 0.99
    Steps = 50

    for episode in range(Episodes):
        EPSILON = InitEpsilon - InitEpsilon*(episode/Episodes)
        Records = SimProc(InitState, InitAction, Policy, Steps, GAMMA, Reward, TransMat)
        NowEpisodeValue, NowEpisodeVisited = EpisodeValue(Records, GAMMA)
        ActionValue, TotalVisited = UpdateActionValue(NowEpisodeValue, NowEpisodeVisited,
                ActionValue, TotalVisited)
        Policy, Explore = NextPolicy(ActionValue, EPSILON)
        PrintPolicy(episode, Policy, Explore)
        time.sleep(1)

        # next simulating InitState and InitAction
        InitState = np.random.randint(1,16)
        InitAction = Policy[InitState]

關於蒙地卡羅控制的完整程式碼,請見 GitHub

執行結果

這邊設定 episodes = 1000, 起始狀態 = 3, 起始動作 = 左, 起始 EPSILON = 0.5,在這個參數組下,進行了幾次模擬,比較好的結果像這個樣子

============================================================
[Policy]
Policy Type: Greedy
Episode: 1000
[['*' '<' '<' 'v']
 ['^' '<' '<' 'v']
 ['^' '>' 'v' 'v']
 ['>' '>' '>' '*']]
============================================================

比較差一點的像這個樣子:

============================================================
[Policy]
Policy Type: Greedy
Episode: 1000
[['*' '<' '<' '<']
 ['^' '<' 'v' 'v']
 ['^' '^' '>' 'v']
 ['^' '<' '>' '*']]
============================================================ 

在狀態 13 的地方繞了遠路。

有興趣的話可以自己跑幾次,或調整一些參數,說不定會看到其他有趣的結果。明天來討論為什麼執行結果會有差異,以及蒙地卡羅方法的特色。


上一篇
強化學習筆記 Day 18
下一篇
強化學習筆記 Day 20
系列文
強化學習30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
皮卡喵
iT邦新手 4 級 ‧ 2020-04-23 14:50:10

請問這是[起始狀態 = 3, 起始動作 = 左, 起始 EPSILON = 0.5],是說如果手動設定嗎?程式看起來初始狀態跟動作是隨機的。

InitState = np.random.randint(1,16)
InitAction = Policy[InitState]

dbgchamp iT邦新手 5 級 ‧ 2020-04-23 21:29:08 檢舉

不好意思沒有表達清楚,我指的是第一個 episode 的設定,可以參考 GitHub 最後兩行。

而你說的沒錯,在第二次以後確實都是使用隨機決定。這部分是考量到,我使用的是蒙地卡羅算法,因此盡量減少人為決定的事情。

我要留言

立即登入留言